package com.ssm.tool;

import org.apache.commons.lang3.tuple.Pair;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

public class HttpUtils {

    static CloseableHttpClient httpClient;

    /**
     * 使用静态初始化块来初始化httpClient，这意味着httpClient是一个静态变量，
     * 它在类被加载到JVM时只会被初始化一次。这样做的好处是可以在整个应用程序中共享这个httpClient实例，减少资源消耗并提高性能。
     */
   // !!!!!重点!!!!!
    static {
        // 注册http和https的相关内容(注册连接套接字工厂)
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory()) //对于HTTP协议，使用PlainConnectionSocketFactory.getSocketFactory()获取默认的套接字工厂。
                .register("https", SSLConnectionSocketFactory.getSocketFactory()) //对于HTTPS协议，使用SSLConnectionSocketFactory.getSocketFactory()获取支持SSL/TLS的套接字工厂
                .build();

        // 创建连接池并设置相关属性
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
        connectionManager.setMaxTotal(500); //设置连接池的最大总连接数为500，这意味着在任何给定时间，连接池可以持有的最大连接数是500。
        connectionManager.setDefaultMaxPerRoute(500); //设置每个路由（可以理解为每个目标主机）的最大连接数为500，这允许对单个目标进行大量的并发连接。
        connectionManager.setDefaultSocketConfig(
                SocketConfig.custom()
                        .setSoTimeout(15, TimeUnit.SECONDS) //配置套接字选项，如设置套接字超时时间为15秒，启用TCP无延迟选项等。
                        .build()
        );
        connectionManager.setValidateAfterInactivity(TimeValue.ofSeconds(15)); //设置连接在不活动15秒后验证其有效性，这有助于及时释放无效连接。

        // 配置RequestConfig(用于配置请求的超时时间和其他参数)
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(Timeout.ofSeconds(1)) //设置连接超时时间为1秒，即等待与服务器建立连接的最长时间。
                .setConnectionRequestTimeout(Timeout.ofSeconds(1)) //设置连接请求超时时间为1秒，即等待从连接管理器获取可用连接的最长时间。
                .setResponseTimeout(Timeout.ofSeconds(1)) //设置响应超时时间为1秒，即从服务器读取响应的最长时间。
                .build();

        // 创建HttpClients(开始自定义HttpClient的配置)
        httpClient = HttpClients.custom()
                .setConnectionManager(connectionManager) //设置连接管理器为前面创建的connectionManager
                .setDefaultRequestConfig(requestConfig) //设置默认的请求配置为前面创建的requestConfig
                .disableAutomaticRetries() //禁用自动重试机制，这意味着如果请求失败（如超时、连接问题等），HttpClient不会自动重试请求
                .build();
    }

    /**
     * 自定义封装方法(post方法)
     * @param url 请求路径
     * @param pairList pair二元组，key为请求参数的key value为请求参数的值(使用List是因为pair无法foreach遍历)
     * @param headerMap 请求头信息，可能要我们手动添加请求头参数
     * @return
     */
    public static String post(String url, List<Pair<String, String>> pairList, Map<String, String> headerMap) throws Exception {
        url = url + "?" + buildParam(pairList);

        HttpPost httpPost = new HttpPost(url); //创建post请求对象

        if(Objects.nonNull(headerMap) && !headerMap.isEmpty()) {
            //当headerMap不为空时，要把map放到请求头中(可使用MapUtils代替)
            headerMap.forEach((key, value) -> {
                httpPost.addHeader(key, value);
            });
        }

        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpPost); //发送请求，获取响应对象
            return EntityUtils.toString(response.getEntity()); //将response结果转化为实体返回
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if(response != null) {
                EntityUtils.consume(response.getEntity()); //释放资源
            }
        }
    }

    //将请求参数拼接到请求路径上 xxx? name=ssm&age=18 拼接问号后面的内容
    private static String buildParam(List<Pair<String, String>> pairList) {
        StringBuilder stringBuilder = new StringBuilder();
        for(Pair<String, String> pair : pairList) {
            stringBuilder.append(pair.getKey()).append("=").append(pair.getValue()).append("$");
        }
        stringBuilder.setLength(stringBuilder.length() - 1); //把最后的$删除
        return stringBuilder.toString();
    }
}
